package com.example.boot.config;

import com.example.boot.modules.auth.component.MobilePasswordTokenGranter;
import com.example.starter.security.component.ExampleAuthorizationTokenServices;
import com.example.starter.security.component.NoEncodePasswordEncoder;
import com.example.starter.security.constant.SqlScript;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;

import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @author 王令
 * @since 2023/2/14 12:16
 */
@Configuration
@EnableAuthorizationServer
@RequiredArgsConstructor
public class OAuth2AuthorizationConfiguration extends AuthorizationServerConfigurerAdapter {

    private final AuthenticationManager authenticationManager;
    private final DataSource dataSource;
    private final TokenStore tokenStore;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetailsService());
    }

    @SneakyThrows
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        List<TokenGranter> tokenGranters = new ArrayList<>(Collections.singletonList(endpoints.getTokenGranter()));
        tokenGranters.add(new MobilePasswordTokenGranter(authenticationManager, tokenServices(), clientDetailsService(),
                new DefaultOAuth2RequestFactory(clientDetailsService())));
        endpoints
                .authenticationManager(authenticationManager)
                .tokenServices(tokenServices())
                .allowedTokenEndpointRequestMethods(HttpMethod.POST)
                .tokenGranter(new CompositeTokenGranter(tokenGranters))
                .pathMapping("/oauth/token", "/auth/oauth/token");
    }

    @SneakyThrows
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security
                // /oauth/token_key
                .tokenKeyAccess("permitAll()")
                // /oauth/check_token
                .checkTokenAccess("permitAll()")
                .passwordEncoder(new NoEncodePasswordEncoder())
                .allowFormAuthenticationForClients();
    }

    public ClientDetailsService clientDetailsService() {
        JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
        clientDetailsService.setPasswordEncoder(new NoEncodePasswordEncoder());
        clientDetailsService.setInsertClientDetailsSql(SqlScript.INSERT_STATEMENT);
        clientDetailsService.setUpdateClientDetailsSql(SqlScript.UPDATE_STATEMENT);
        clientDetailsService.setDeleteClientDetailsSql(SqlScript.DELETE_STATEMENT);
        clientDetailsService.setFindClientDetailsSql(SqlScript.FIND_STATEMENT);
        clientDetailsService.setSelectClientDetailsSql(SqlScript.SELECT_STATEMENT);
        clientDetailsService.setUpdateClientSecretSql(SqlScript.UPDATE_SECRET_STATEMENT);
        return clientDetailsService;
    }

    public AuthorizationServerTokenServices tokenServices() {
        ExampleAuthorizationTokenServices services = new ExampleAuthorizationTokenServices();
        services.setClientDetailsService(clientDetailsService());
        services.setTokenStore(tokenStore);
        services.setSupportRefreshToken(true);
        return services;
    }
}
